package hikari.gui.panels;

import com.gooddies.ballontips.ToolTipHandler;
import com.gooddies.events.ValueChangedEvent;
import com.gooddies.swing.hCheckBox;
import com.gooddies.swing.hComboBox;
import com.gooddies.swing.hList;
import com.gooddies.texteditors.DefaultFloatTextField;
import com.gooddies.wiring.Wiring;
import com.gooddies.wiring.annotations.PostWiring;
import com.gooddies.wiring.annotations.Wire;
import com.gooddies.wiring.annotations.WiringComponent;
import hikari.gui.assets.AssetsTree;
import hikari.gui.guicomponents.ColorPicker;
import hikari.gui.guicomponents.ExpandablePanel;
import hikari.gui.guicomponents.easydrag.DefaultDrop;
import hikari.gui.guicomponents.easydrag.listeners.DropListener;
import hikari.gui.guicomponents.hLabel;
import hikari.hcomponents.HComponent;
import hikari.managers.Manager3D;
import hikari.objects3d.Basic3dObject;
import hikari.utils.Utils;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.tree.TreePath;
import net.miginfocom.swing.MigLayout;
import org.swig.jirr.E_MATERIAL_TYPE;
import org.swig.jirr.ITexture;
import org.swig.jirr.MATERIAL_FLAG;
import org.swig.jirr.SColor;
import org.swig.jirr.SMaterial;

/**
 * @author sad
 */
@WiringComponent(singleton = true, lazy = true)
public class MaterialPropertiesPanel extends HComponent {

    private Basic3dObject model;
    private ArrayList<hCheckBox> checkBoxes = new ArrayList<>();
    private ActionListener onCheck;
    private ExpandablePanel materialFlagsPanel;
    private ExpandablePanel texturesPanel;
    private hList texturesList;
    private ColorPicker ambientColorPick;
    private ColorPicker diffuseColorPick;
    private ColorPicker specularColorPick;
    private ColorPicker emissiveColorPick;
    private DefaultFloatTextField shininessBox;
    private hComboBox<E_MATERIAL_TYPE> materialTypeCB;
    private hCheckBox visibleCB;
    @Wire
    private AssetsTree assetsTree;
    @Wire
    private Manager3D manager3D;
    @Wire
    private Utils utils;

    private MaterialPropertiesPanel() {
    }

    @PostWiring
    private void init() {
        setTitle("Material Properties");
        final MigLayout layout = new MigLayout("insets 5,fillx");
        setLayout(layout);
        onCheck = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                hCheckBox checkBox = (hCheckBox) e.getSource();
                MATERIAL_FLAG flag = (MATERIAL_FLAG) checkBox.getClientProperty("flag");
                MaterialPropertiesPanel.this.model.getMeshSceneNode().setMaterialFlag(flag, checkBox.isSelected());
            }
        };

        add(new hLabel("Ambient color"));
        ambientColorPick = new ColorPicker();
        ambientColorPick.setValueChangedEvent(new ValueChangedEvent<Color>() {
            @Override
            protected void valueChanged(Color color) {
                SMaterial material = getMaterial();
                material.setAmbientColor(new SColor(color));
                model.getMeshSceneNode().setAmbientChanged(true);
            }
        });
        add(ambientColorPick, "wrap");

        add(new hLabel("Diffuse color"));
        diffuseColorPick = new ColorPicker();
        diffuseColorPick.setValueChangedEvent(new ValueChangedEvent<Color>() {
            @Override
            protected void valueChanged(Color color) {
                SMaterial material = getMaterial();
                material.setDiffuseColor(new SColor(color));
                model.getMeshSceneNode().setDiffuseChanged(true);
            }
        });

        add(diffuseColorPick, "wrap");

        add(new hLabel("Emissive color"));
        emissiveColorPick = new ColorPicker();
        emissiveColorPick.setValueChangedEvent(new ValueChangedEvent<Color>() {
            @Override
            protected void valueChanged(Color color) {
                SMaterial material = getMaterial();
                material.setEmissiveColor(new SColor(color));
                model.getMeshSceneNode().setEmmisiveChanged(true);
            }
        });
        add(emissiveColorPick, "wrap");

        add(new hLabel("Specular color"));
        specularColorPick = new ColorPicker();
        specularColorPick.setValueChangedEvent(new ValueChangedEvent<Color>() {
            @Override
            protected void valueChanged(Color color) {
                SMaterial material = getMaterial();
                material.setSpecularColor(new SColor(color));
                model.getMeshSceneNode().setSpecularChanged(true);
            }
        });
        add(specularColorPick, "wrap");

        add(new hLabel("Shininess"));
        shininessBox = new DefaultFloatTextField(0, 1);
        shininessBox.setValueChangedEventNumber(new ValueChangedEvent<Float>() {
            @Override
            public void valueChanged(Float value) {
                SMaterial material = getMaterial();
                material.setShininess(value);
                model.getMeshSceneNode().setShinessChanged(true);
            }
        });
        add(shininessBox, "wrap");
        materialTypeCB = new hComboBox<>(E_MATERIAL_TYPE.values());
        materialTypeCB.setPopupWidth(500);
        materialTypeCB.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    SMaterial material = getMaterial();
                    material.setMaterialType((E_MATERIAL_TYPE) materialTypeCB.getSelectedItem());
                }
            }
        });
        add(new hLabel("Material type"));
        add(materialTypeCB, "w 100!, wrap");

        visibleCB = new hCheckBox("Visible");
        visibleCB.setValueChangedEvent(new ValueChangedEvent<Boolean>() {
            @Override
            public void valueChanged(Boolean value) {
                setNodeVisible(value);
            }
        });
        add(visibleCB, "w 100!, wrap");

        texturesPanel = Wiring.getComponent(ExpandablePanel.class);
        texturesPanel.setLayout(new MigLayout("fillx"));
        texturesPanel.setTitle("Textures");
        texturesList = new hList();
        texturesPanel.add(texturesList, "spanx 10, growx, wrap");
        JButton removeTexture = new JButton("Remove");
        removeTexture.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                removeSelectedTexture();
            }
        });
        texturesPanel.add(removeTexture);
        add(texturesPanel, "grow, spanx 10,wrap");

        materialFlagsPanel = Wiring.getComponent(ExpandablePanel.class);
        materialFlagsPanel.setOnExpandPanel(new ValueChangedEvent<Boolean>() {
            @Override
            public void valueChanged(Boolean value) {
                updateMaterialFlags(getMaterial());
            }
        });
        materialFlagsPanel.setLayout(new MigLayout());
        materialFlagsPanel.setTitle("Material Flags");
        createFlagCheckBox("Anisotropic filter", MATERIAL_FLAG.EMF_ANISOTROPIC_FILTER, materialFlagsPanel);
        createFlagCheckBox("Anti Aliasing", MATERIAL_FLAG.EMF_ANTI_ALIASING, materialFlagsPanel);
        createFlagCheckBox("Back face culling", MATERIAL_FLAG.EMF_BACK_FACE_CULLING, materialFlagsPanel);
        createFlagCheckBox("Bilinear filter", MATERIAL_FLAG.EMF_BILINEAR_FILTER, materialFlagsPanel);
        createFlagCheckBox("Blend operation", MATERIAL_FLAG.EMF_BLEND_OPERATION, materialFlagsPanel);
        createFlagCheckBox("Color mask", MATERIAL_FLAG.EMF_COLOR_MASK, materialFlagsPanel);
        createFlagCheckBox("Color material", MATERIAL_FLAG.EMF_COLOR_MATERIAL, materialFlagsPanel);
        createFlagCheckBox("Fog enable", MATERIAL_FLAG.EMF_FOG_ENABLE, materialFlagsPanel);
        createFlagCheckBox("Front face culling", MATERIAL_FLAG.EMF_FRONT_FACE_CULLING, materialFlagsPanel);
        createFlagCheckBox("Gouraud shading", MATERIAL_FLAG.EMF_GOURAUD_SHADING, materialFlagsPanel);
        createFlagCheckBox("Lighting", MATERIAL_FLAG.EMF_LIGHTING, materialFlagsPanel);
        createFlagCheckBox("Normalize normals", MATERIAL_FLAG.EMF_NORMALIZE_NORMALS, materialFlagsPanel);
        createFlagCheckBox("Point cloud", MATERIAL_FLAG.EMF_POINTCLOUD, materialFlagsPanel);
        createFlagCheckBox("Polygon offset", MATERIAL_FLAG.EMF_POLYGON_OFFSET, materialFlagsPanel);
        createFlagCheckBox("Texture wrap", MATERIAL_FLAG.EMF_TEXTURE_WRAP, materialFlagsPanel);
        createFlagCheckBox("Trilinear filter", MATERIAL_FLAG.EMF_TRILINEAR_FILTER, materialFlagsPanel);
        createFlagCheckBox("Use mip maps", MATERIAL_FLAG.EMF_USE_MIP_MAPS, materialFlagsPanel);
        createFlagCheckBox("Wireframe", MATERIAL_FLAG.EMF_WIREFRAME, materialFlagsPanel);
        createFlagCheckBox("ZBuffer", MATERIAL_FLAG.EMF_ZBUFFER, materialFlagsPanel);
        createFlagCheckBox("ZWrite enable", MATERIAL_FLAG.EMF_ZWRITE_ENABLE, materialFlagsPanel);
        add(materialFlagsPanel, "grow, spanx 10,wrap");
        enableDropTextureToTexturesList();
        setOnExpandPanel(new ValueChangedEvent<Boolean>() {
            @Override
            public void valueChanged(Boolean value) {
                if (value == true) {
                    updateMaterial();
                }
            }
        });
    }

    private void setNodeVisible(boolean visible) {
        model.setVisible(visible);
    }

    private void enableDropTextureToTexturesList() {
        DefaultDrop drop = new DefaultDrop(this, new DropListener() {
            @Override
            public boolean canDrop(Component source, Component target, Object data, int x, int y) {
                return source instanceof AssetsTree;
            }

            @Override
            public void performDrop(Component source, Component target, DropTargetDropEvent dtde, Object data) {

                TreePath draggedPath = (TreePath) data;
                String filePath = assetsTree.treePathToFilePath(draggedPath);
                String extension = utils.getFileExtension(filePath);
                if (!assetsTree.isExtensionTexture(extension)) {
                    ToolTipHandler.error(texturesList, "File with extension " + extension + " is not a supported texture");
                    return;
                }

                addTexture(filePath);
            }
        });

        drop.setEnabled(true);
    }

    private void addTexture(String texturePath) {
        SMaterial selectedMaterial = getMaterial();

        int texturesCount = selectedMaterial.getTexturesCount();
        if (texturesCount == SMaterial.MAX_TEXTURES_COUNT) {
            ToolTipHandler.error(texturesList, "Only " + SMaterial.MAX_TEXTURES_COUNT + " allowed per material");
            return;
        }

        ITexture texture = manager3D.getVideoDriver().getTexture(texturePath);
        if (texture == null) {
            ToolTipHandler.error(texturesList, "Cannot load texture");
            return;
        }

        model.getMeshSceneNode().setTexturesChanged(true);
        selectedMaterial.setTexture(texturesCount, texture);
        updateTextureList();
    }

    private void removeSelectedTexture() {
        SMaterial material = getMaterial();

        int texturesCount = material.getTexturesCount();
        if (texturesCount == 0) {
            ToolTipHandler.error(texturesList, "There is no texture to delete");
            return;
        }
        int selectedTexture = getSelectedTextureIndex();
        if (selectedTexture == -1) {
            ToolTipHandler.error(texturesList, "Please select texture first");
            return;
        }

        model.getMeshSceneNode().setTexturesChanged(true);
        material.setTexture(selectedTexture, null);
        for (int i = selectedTexture; i < texturesCount; i++) {
            if (i == texturesCount - 1) {
                material.setTexture(i, null);
            } else {
                material.setTexture(i, material.getTexture(i + 1));
            }
        }
        updateTextureList();
    }

    private int getSelectedTextureIndex() {
        int textureIndex = texturesList.getSelectedIndex();
        return textureIndex;
    }

    public void initialUpdateFromModel(Basic3dObject node) {
        model = node;
        if (isExpanded()) {
            updateMaterial();
        }
    }

    public SMaterial getMaterial() {
        return model.getMeshSceneNode().getMaterial(0);
    }

    private void updateMaterial() {
        SMaterial material = getMaterial();
        ambientColorPick.setSelectedColor(material.getAmbientColor());
        diffuseColorPick.setSelectedColor(material.getDiffuseColor());
        emissiveColorPick.setSelectedColor(material.getEmissiveColor());
        specularColorPick.setSelectedColor(material.getSpecularColor());
        shininessBox.setValue(material.getShininess());
        materialTypeCB.setSelectedItem(material.getMaterialType());
        visibleCB.setValue(model.isVisible());
        updateMaterialFlags(material);
        updateTextureList();
    }

    @SuppressWarnings("unchecked")
    private void updateTextureList() {
        SMaterial material = getMaterial();
        texturesList.clearItems();
        for (int i = 0; i < SMaterial.MAX_TEXTURES_COUNT; i++) {
            ITexture texture = material.getTexture(i);
            if (texture != null) {
                String texturePath = texture.getName().getInternalName();
                String name = assetsTree.getRelativeAssetsPath(texturePath);
                texturesList.addItem(name);
            } else {
                break;
            }
        }
    }

    private void updateMaterialFlags(SMaterial material) {
        if (material != null) {
            if (materialFlagsPanel.isExpanded()) {
                for (hCheckBox cb : checkBoxes) {
                    MATERIAL_FLAG flag = (MATERIAL_FLAG) cb.getClientProperty("flag");
                    boolean state = material.getFlag(flag);
                    cb.setSelected(state);
                    cb.setEnabled(true);
                }
            }
        } else {
            for (hCheckBox cb : checkBoxes) {
                cb.setSelected(false);
                cb.setEnabled(false);
            }
        }
    }

    private void createFlagCheckBox(String title, MATERIAL_FLAG flag, JPanel panel) {
        hCheckBox checkBox = new hCheckBox(title);
        checkBox.setPreferredSize(new Dimension(100, 20));
        checkBox.putClientProperty("flag", flag);
        checkBox.addActionListener(onCheck);
        panel.add(checkBox, "wrap");
        checkBoxes.add(checkBox);
    }
}
